using Crux.Core.Websockets.Metadata;
using Microsoft.Extensions.Options;
using System.Net.WebSockets;
using System.Text;

namespace Crux.Core.Websockets;

/// <summary>
/// WebSocketConnectionHandlerOfT
/// </summary>
public class WebSocketConnectionHandler<THandler>(
    IOptions<WebSocketOptions<THandler>> options,
    ILogger<WebSocketConnectionHandler<THandler>> logger,
    WebSocketLifetimeManager<THandler> lifetimeManager,
    WebSocketDispatcher<THandler> webSocketDispatcher)
    : ConnectionHandler
    where THandler : WebSocketHandler
{
    #region fields

    /// <summary>
    ///
    /// </summary>
    private readonly ILifetimeManager _manger = lifetimeManager ?? throw new ArgumentNullException(nameof(lifetimeManager));

    /// <summary>
    ///
    /// </summary>
    private readonly ILogger<WebSocketConnectionHandler<THandler>> _logger = logger ?? throw new ArgumentNullException(nameof(logger));

    /// <summary>
    ///
    /// </summary>
    private readonly WebSocketDispatcher<THandler> _dispatcher = webSocketDispatcher ?? throw new ArgumentNullException(
                                                                             nameof(webSocketDispatcher));

    /// <summary>
    ///
    /// </summary>
    private readonly IOptions<WebSocketOptions<THandler>> options = options ?? throw new ArgumentNullException(nameof(options));

    #endregion

    #region implements ConnectionHandler
    /// <summary>
    ///
    /// </summary>
    public override async Task OnConnectedAsync(WebSocketConnectionContext context)
    {
        Requires.NotNull(context, nameof(context));

        Action<Task> exceptionHandle = _ => _.Exception?.Handle(e =>
        {
            _logger.LogError(e, e.Message);
            return true;
        });

        THandler? handler = null;
        try
        {
            await _manger.OnConnectedAsync(context).ContinueWith(exceptionHandle).ConfigureAwait(false);

            handler = _dispatcher.CreateHandler(context.HttpContext);
            InitializeWebHandler(handler);

            await handler.OnConnectedAsync(context).ContinueWith(exceptionHandle).ConfigureAwait(false);

            await RunWebHandler(handler, context);
        }
        catch (Exception e)
        {
            _logger.LogError(e, e.Message);
        }
        finally
        {
            if (handler is not null)
                await handler.OnDisconnectedAsync(context).ContinueWith(exceptionHandle).ConfigureAwait(false);

            await _manger.OnDisconnectedAsync(context).ContinueWith(exceptionHandle).ConfigureAwait(false);
        }
    }

    #endregion implements


    /// <summary>
    ///
    /// </summary>
    private void InitializeWebHandler(THandler handler)
    {
        Requires.NotNull(handler, nameof(handler));

        handler.Clients = new WebSocketClientProxy(this._manger);
        handler.Groups = new GroupProxy(this._manger);
    }

    private static readonly Message Health = new Message
    {
        Type = MessageType.HealthCheck
    };

    private static readonly ErrorInvocationMessage ErrorInvocationMessage = new ErrorInvocationMessage
    {
        Type = MessageType.InvocationResponse,
        ErrorMessage = "NotFoundMessageType"
    };

    /// <summary>
    ///
    /// </summary>
    private Task RunWebHandler(THandler handler, WebSocketConnectionContext context)
        => ReceiveAsync(context.WebSocket, async (result, stream) =>
        {
            Action<Task> handle = (t) =>
            {
                if (t.IsFaulted)
                {
                    if (t.Exception is not null)
                    {
                        _logger.LogError(t.Exception, t.Exception.Message);
                    }

                    t.Exception?.Handle(_ => true);
                }
            };
            switch (result?.MessageType)
            {
                case WebSocketMessageType.Text:
                    byte[] buffer = new byte[stream.Length];
                    stream.Read(buffer, 0, (int)stream.Length);

                    string content = Encoding.UTF8.GetString(buffer);
                    if (string.IsNullOrEmpty(content)) return;

                    var internalMessage = JsonConvert.DeserializeObject<InvokeMessage>(content, WebsocketConstants.SerializerSettings);

                    switch (internalMessage?.Type)
                    {
                        case MessageType.HealthCheck:
                            await context.SendAsync(Health.GetBuffer())
                                .ContinueWith(handle)
                                .ConfigureAwait(false);
                            break;

                        case MessageType.Invoke:
                            await _dispatcher.DispatchAsync(handler, context, internalMessage).ContinueWith(
                                async t =>
                                {
                                    if (t.IsFaulted)
                                    {
                                        _logger.LogError(t.Exception.InnerException, t.Exception.Message);

                                        using var message = new ErrorInvocationMessage
                                        {
                                            Type = MessageType.InvocationResponse,
                                            ErrorMessage = t.Exception.Message
                                        };

                                        await context.SendAsync(message.GetBuffer())
                                            .ContinueWith(handle)
                                            .ConfigureAwait(false);
                                    }
                                }).ConfigureAwait(false);
                            break;

                        default:
                            await context.SendAsync(ErrorInvocationMessage.GetBuffer())
                                .ContinueWith(handle)
                                .ConfigureAwait(false);

                            break;
                    }

                    break;

                case WebSocketMessageType.Binary:
                    await handler.HandleBinaryMessageAsync(context, stream).ConfigureAwait(false);

                    break;
            }
        }, context.ConnectionAborted);

    /// <summary>
    ///
    /// </summary>
    private async Task ReceiveAsync(
        WebSocket webSocket,
        Action<WebSocketReceiveResult?, Stream> handle,
        CancellationToken cancellationToken = default)
    {
        Requires.NotNull(handle, nameof(handle));

        var bufferSize = options.Value?.BufferSize ?? WebSocketOptions.DefaultBufferSize;

        var buffer = new byte[bufferSize];

        var bufferSegment = new ArraySegment<byte>(buffer);

        while (Equals(WebSocketState.Open, webSocket.State))
        {
            try
            {
                using var memory = new MemoryStream();

                WebSocketReceiveResult? result = null;
                do
                {
                    result = await webSocket.ReceiveAsync(bufferSegment, cancellationToken).ConfigureAwait(false);

                    memory.Write(bufferSegment.Array ?? Array.Empty<byte>(), bufferSegment.Offset, result.Count);
                } while (!(result?.EndOfMessage ?? true));

                memory.Seek(0, SeekOrigin.Begin);

                handle(result, memory);
            }
            catch (WebSocketException e)
                when (Equals(WebSocketError.ConnectionClosedPrematurely, e.WebSocketErrorCode))
            {
                webSocket.Abort();

                _logger.LogError(e, e.Message);
            }
            catch (OperationCanceledException)
            {
                _logger.LogWarning("客户端取消操作");
            }
            catch (Exception e)
            {
                _logger.LogError(e, e.Message);
            }
        }
    }
}